from sqlalchemy.orm import Session
from app.database.models import File
import hashlib
import logging
from datetime import datetime, timedelta
from sqlalchemy import func, and_, or_
import math
from typing import Optional, List, Dict, Any, Union
from fastapi import HTTPException
from app.utils.minio_client import minio_client
import re
from pathlib import Path
from app.utils.file_metadata import FileMetadataExtractor
from app.core.config import settings
from app.utils.bloom_filter import file_hash_filter
import os

# 配置日志记录器
logger = logging.getLogger(__name__)

# 定义日期时间模式
DATETIME_PATTERNS = [
    # 明确的日期格式，带有分隔符
    (r"(?<![0-9])(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{3})(?![0-9])", "%Y-%m-%d-%H-%M-%S-%f"),  # 2025-03-06-20-26-28-334
    (r"(?<![0-9])(\d{4}-\d{2}-\d{2}-\d{6})(?![0-9])", "%Y-%m-%d-%H%M%S"),  # 2023-08-14-215002
    (r"(?<![0-9])(\d{4}-\d{2}-\d{2})(?![0-9])", "%Y-%m-%d"),  # 2023-08-14
    
    # 下划线分隔的日期时间格式
    (r"(?<![0-9])(\d{8}_\d{6})(?![0-9])", "%Y%m%d_%H%M%S"),  # 20231007_072740
    
    # 连续数字格式，添加更严格的限制条件
    # 年份限制在 1970-2100 之间
    (r"(?<![0-9])(19[7-9][0-9]|20[0-9]{2})(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])([01][0-9]|2[0-3])([0-5][0-9])([0-5][0-9])(?![0-9])", "%Y%m%d%H%M%S"),  # 格式为 YYYYMMDDHHMMSS，如 20210628142507
    (r"(?<![0-9])(19[7-9][0-9]|20[0-9]{2})(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])(?![0-9])", "%Y%m%d")  # 格式为 YYYYMMDD，如 20170923
]

def extract_datetime_from_filename(filename: str) -> Optional[datetime]:
    """从文件名中提取日期时间
    
    Args:
        filename: 文件名
    
    Returns:
        Optional[datetime]: 解析出的日期时间，如果解析失败则返回 None
    """
    # 获取文件名（不含扩展名）
    name = Path(filename).stem
    
    logger.debug(f"尝试从文件名提取日期时间: {name}")
    
    # 尝试所有模式
    for pattern, date_format in DATETIME_PATTERNS:
        match = re.search(pattern, name)
        if match:
            try:
                # 使用捕获组来获取日期字符串
                if len(match.groups()) > 0:
                    date_str = match.group(1)
                else:
                    date_str = match.group(0)
                    
                logger.debug(f"尝试解析日期时间: {date_str} -> {date_format}")
                parsed_date = datetime.strptime(date_str, date_format)
                
                # 额外验证：确保年份在合理范围内
                if 1970 <= parsed_date.year <= 2100:
                    return parsed_date
                else:
                    logger.warning(f"解析出的日期年份超出合理范围: {parsed_date.year}，跳过")
            except ValueError as e:
                logger.debug(f"日期解析失败: {str(e)}")
                continue
    
    logger.debug("未能从文件名提取有效的日期时间")
    return None

def get_file_create_time(temp_file_path: str, original_filename: str) -> datetime:
    """获取文件创建时间，按优先级尝试不同方式
    
    优先级顺序：
    1. 文件名中的日期时间（对于包含日期时间的文件名）
    2. 图片 EXIF 数据中的拍摄时间（对于图片文件）
    3. 文件系统时间
    4. 当前时间（作为最后备选）
    
    Args:
        temp_file_path: 临时文件路径
        original_filename: 原始文件名
    
    Returns:
        datetime: 文件创建时间
    """
    create_time = None
    
    # 1. 首先尝试从文件名解析时间
    create_time = extract_datetime_from_filename(original_filename)
    if create_time:
        logger.debug(f"从文件名解析到创建时间: {create_time}")
        return create_time
    else:
        logger.debug("未从文件名解析到时间，尝试其他方式")
    
    # 2. 尝试从 EXIF 数据获取拍摄时间（仅对图片文件）
    try:
        # 检查文件扩展名是否为图片
        ext = Path(original_filename).suffix.lower()
        if ext in ['.jpg', '.jpeg', '.png', '.tiff']:
            create_time = FileMetadataExtractor.extract_create_time(temp_file_path)
            if create_time:
                logger.debug(f"从 EXIF 数据获取到拍摄时间: {create_time}")
                return create_time
            else:
                logger.debug("未从 EXIF 数据获取到拍摄时间，尝试使用文件系统时间")
    except Exception as e:
        logger.warning(f"读取 EXIF 数据失败: {str(e)}")
    
    # 3. 使用文件系统时间作为备选
    try:
        create_time = FileMetadataExtractor.extract_create_time(temp_file_path)
        if create_time:
            logger.debug(f"使用文件系统时间: {create_time}")
            return create_time
    except Exception as e:
        logger.warning(f"获取文件系统时间失败: {str(e)}")
    
    # 4. 最后使用当前时间作为默认值
    create_time = datetime.now()
    logger.debug(f"使用当前时间作为默认值: {create_time}")
    return create_time

def build_storage_path(user_id: int, file_type: str, create_time: datetime, original_filename: str, file_hash: str, directory_id: int = 0) -> str:
    """构建存储路径
    
    Args:
        user_id: 用户ID
        file_type: 文件类型
        create_time: 创建时间
        original_filename: 原始文件名
        file_hash: 文件哈希值
        directory_id: 目录ID，默认为0（根目录）
    
    Returns:
        str: 存储路径（不包含 bucket 名称）
    """
    # 分离文件名和扩展名
    name = Path(original_filename).stem
    ext = Path(original_filename).suffix
    
    # 构建新的文件名：原始文件名_hash值前8位.扩展名
    new_filename = f"{name}_{file_hash[:8]}{ext}"
    
    # 使用新的存储路径格式
    storage_path = f"{user_id}/dir_{directory_id}/{new_filename}"
    logger.debug(f"构建存储路径: {storage_path}")
    return storage_path

def save_file_info(
    db: Session, 
    user_id: int, 
    original_filename: str, 
    temp_file_path: str, 
    file_type: str, 
    file_size: int, 
    file_hash: str, 
    mime_type: str,
    extension: str,
    directory_id: int = 0
):
    """保存文件信息到数据库和MinIO
    
    Args:
        db: 数据库会话
        user_id: 用户ID
        original_filename: 原始文件名
        temp_file_path: 临时文件路径
        file_type: 文件类型
        file_size: 文件大小
        file_hash: 文件哈希值
        mime_type: MIME类型
        extension: 文件扩展名
        directory_id: 目录ID，默认为0（根目录）
    """
    # 获取文件创建时间（按优先级：EXIF数据 -> 文件名 -> 文件系统时间）
    create_time = get_file_create_time(temp_file_path, original_filename)
    
    # 构建存储路径
    storage_path = build_storage_path(
        user_id=user_id, 
        file_type=file_type, 
        create_time=create_time, 
        original_filename=original_filename, 
        file_hash=file_hash,
        directory_id=directory_id
    )
    
    # 上传文件到MinIO
    with open(temp_file_path, 'rb') as file_object:
        try:
            result = minio_client.upload_file(
                file_object=file_object,
                object_path=storage_path,
                content_type=mime_type
            )
            logger.debug(f"MinIO上传结果: {result}")
            
            # 将文件哈希添加到布隆过滤器
            file_hash_filter.add(file_hash)
            
            # 保存到数据库
            file_info = File(
                user_id=user_id,
                file_name=original_filename,
                file_path=storage_path,
                file_type=file_type,
                file_size=file_size,
                hash=file_hash,
                create_time=create_time,
                extension=extension,
                mime_type=mime_type,
                is_deleted=False,
                directory_id=directory_id
            )
            
            db.add(file_info)
            db.commit()
            db.refresh(file_info)
            
            logger.debug(f"文件信息已保存到数据库，ID: {file_info.id}")
            return file_info
        except Exception as e:
            logger.error(f"MinIO上传失败: {str(e)}")
            raise HTTPException(status_code=500, detail=f"文件上传失败: {str(e)}")

def calculate_file_hash(file_content_or_path):
    """计算文件哈希值（SHA256）
    
    参数:
        file_content_or_path: 可以是文件内容（bytes）或文件路径（str）
        
    返回:
        str: 文件的SHA256哈希值
    """
    sha256 = hashlib.sha256()
    
    # 如果参数为None或空值，抛出明确的错误
    if file_content_or_path is None:
        raise ValueError("计算哈希值时缺少文件内容或路径")
    
    if isinstance(file_content_or_path, bytes):
        # 如果参数是字节内容，直接更新哈希
        sha256.update(file_content_or_path)
    elif isinstance(file_content_or_path, str):
        # 如果参数是字符串路径，读取文件内容并更新哈希
        try:
            if not os.path.exists(file_content_or_path):
                raise FileNotFoundError(f"文件不存在: {file_content_or_path}")
                
            with open(file_content_or_path, 'rb') as f:
                # 分块读取文件，避免大文件一次性加载到内存
                for chunk in iter(lambda: f.read(4096), b''):
                    sha256.update(chunk)
        except Exception as e:
            logger.error(f"读取文件计算哈希失败: {str(e)}")
            raise
    else:
        # 参数类型错误
        raise TypeError(f"file_content_or_path 必须是字节或字符串类型，实际类型: {type(file_content_or_path)}")
    
    file_hash = sha256.hexdigest()
    logger.debug(f"计算文件哈希值: {file_hash}")
    return file_hash

def find_duplicate_file(db: Session, user_id: int, file_hash: str):
    """查找用户是否已上传过相同哈希值的文件"""
    duplicate = db.query(File).filter(
        File.user_id == user_id,
        File.hash == file_hash,
        File.is_deleted == False
    ).first()
    
    if duplicate:
        logger.debug(f"找到重复文件: {duplicate.file_path}")
    else:
        logger.debug("未找到重复文件")
        
    return duplicate

def search_files(
    db: Session,
    user_id: int,
    file_type: Optional[str] = None,
    start_time: Optional[str] = None,
    end_time: Optional[str] = None,
    keyword: Optional[str] = None,
    file_hash: Optional[str] = None,
    page: int = 1,
    page_size: int = 20,
    is_delete: Optional[bool] = None,
    order_by: str = "upload_time",
    order_desc: bool = True,
    directory_id: Optional[int] = None
) -> Dict[str, Any]:
    """搜索文件
    
    Args:
        db: 数据库会话
        user_id: 用户ID
        file_type: 文件类型
        start_time: 开始时间
        end_time: 结束时间
        keyword: 文件名关键词
        file_hash: 文件哈希值
        page: 页码
        page_size: 每页数量
        is_delete: 是否已删除
        order_by: 排序字段
        order_desc: 是否降序
        directory_id: 目录ID，为None表示不限制目录，为0表示根目录
        
    Returns:
        Dict[str, Any]: 查询结果，包含总数和文件列表
    """
    # 构建查询条件
    conditions = [File.user_id == user_id]
    
    # 删除状态过滤
    if is_delete is not None:
        conditions.append(File.is_deleted == is_delete)
    
    # 文件类型过滤
    if file_type:
        conditions.append(File.file_type == file_type)
    
    # 日期范围过滤
    if start_time:
        start_datetime = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
        conditions.append(File.upload_time >= start_datetime)
    
    if end_time:
        end_datetime = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
        conditions.append(File.upload_time <= end_datetime)
    
    # 文件名关键词过滤
    if keyword:
        conditions.append(File.file_name.like(f"%{keyword}%"))
    
    # 哈希值过滤
    if file_hash:
        conditions.append(File.hash == file_hash)
    
    # 目录ID过滤
    if directory_id is not None:
        conditions.append(File.directory_id == directory_id)
    
    # 计算总数
    total = db.query(func.count(File.id)).filter(*conditions).scalar()
    
    # 分页查询
    query = db.query(File).filter(*conditions)
    
    # 排序
    if order_by == "upload_time":
        query = query.order_by(File.upload_time.desc() if order_desc else File.upload_time.asc())
    elif order_by == "file_size":
        query = query.order_by(File.file_size.desc() if order_desc else File.file_size.asc())
    elif order_by == "file_name":
        query = query.order_by(File.file_name.desc() if order_desc else File.file_name.asc())
    elif order_by == "create_time":
        query = query.order_by(File.create_time.desc() if order_desc else File.create_time.asc())
    
    # 设置分页
    offset = (page - 1) * page_size
    files = query.offset(offset).limit(page_size).all()
    
    # 返回结果
    return {
        "total": total,
        "page": page,
        "page_size": page_size,
        "pages": math.ceil(total / page_size),
        "files": files
    }

def get_file_by_id(db: Session, file_id: int, user_id: int = None):
    """根据ID获取文件信息"""
    query = db.query(File).filter(File.id == file_id)
    if user_id is not None:
        query = query.filter(File.user_id == user_id)
    return query.first()

def delete_file(db: Session, file_id: int, user_id: int, permanent: bool = False):
    """删除文件
    
    Args:
        db: 数据库会话
        file_id: 文件ID
        user_id: 用户ID（用于权限验证）
        permanent: 是否永久删除
    """
    file = get_file_by_id(db, file_id, user_id)
    if not file:
        logger.warning(f"文件不存在或无权访问: file_id={file_id}, user_id={user_id}")
        raise HTTPException(status_code=404, detail="文件不存在或无权访问")
    
    if permanent:
        # 从MinIO中删除文件
        try:
            minio_client.remove_object(file.file_path)
            logger.debug(f"从MinIO中删除文件: {file.file_path}")
        except Exception as e:
            logger.error(f"从MinIO删除文件失败: {str(e)}")
            raise HTTPException(status_code=500, detail="删除文件失败")
        
        # 从数据库中删除记录
        db.delete(file)
    else:
        # 标记为已删除（移入回收站）
        file.is_deleted = True
    
    db.commit()
    logger.debug(f"{'永久' if permanent else '临时'}删除文件: {file_id}")
    return {"message": "删除成功"}

def batch_delete_files(db: Session, file_ids: List[int], user_id: int, permanent: bool = False) -> Dict[str, Any]:
    """批量删除文件
    
    Args:
        db: 数据库会话
        file_ids: 要删除的文件ID列表
        user_id: 用户ID
        permanent: 是否永久删除
        
    Returns:
        Dict[str, Any]: 包含成功和失败信息的字典
    """
    logger.info(f"批量删除文件: user_id={user_id}, file_ids={file_ids}, permanent={permanent}")
    
    results = {
        "success_count": 0,
        "failed_count": 0,
        "failed_files": []
    }
    
    for file_id in file_ids:
        try:
            file = get_file_by_id(db, file_id, user_id)
            if not file:
                logger.warning(f"文件不存在或无权访问: file_id={file_id}, user_id={user_id}")
                results["failed_count"] += 1
                results["failed_files"].append({
                    "file_id": file_id,
                    "reason": "文件不存在或无权访问"
                })
                continue
                
            if permanent:
                # 永久删除文件
                minio_client.remove_object(file.file_path)
                db.delete(file)
                logger.info(f"永久删除文件: file_id={file_id}")
            else:
                # 标记为已删除
                file.is_deleted = True
                file.delete_time = datetime.now()
                logger.info(f"标记文件为已删除: file_id={file_id}")
                
            results["success_count"] += 1
            
        except Exception as e:
            logger.error(f"删除文件出错: file_id={file_id}, error={str(e)}")
            results["failed_count"] += 1
            results["failed_files"].append({
                "file_id": file_id,
                "reason": str(e)
            })
    
    db.commit()
    logger.info(f"批量删除完成: 成功={results['success_count']}, 失败={results['failed_count']}")
    return results

def rename_file(db: Session, file_id: int, user_id: int, new_name: str):
    """重命名文件
    
    Args:
        db: 数据库会话
        file_id: 文件ID
        user_id: 用户ID（用于权限验证）
        new_name: 新文件名
    """
    file = get_file_by_id(db, file_id, user_id)
    if not file:
        logger.warning(f"文件不存在或无权访问: file_id={file_id}, user_id={user_id}")
        raise HTTPException(status_code=404, detail="文件不存在或无权访问")
    
    if file.is_deleted:
        logger.warning(f"文件已在回收站中: file_id={file_id}")
        raise HTTPException(status_code=400, detail="文件已在回收站中")
    
    # 更新文件名
    file.file_name = new_name
    db.commit()
    
    logger.debug(f"重命名文件 {file_id} 为: {new_name}")
    return {"message": "重命名成功", "new_name": new_name}

def restore_file(db: Session, file_id: int, user_id: int):
    """从回收站恢复文件
    
    Args:
        db: 数据库会话
        file_id: 文件ID
        user_id: 用户ID（用于权限验证）
    """
    file = get_file_by_id(db, file_id, user_id)
    if not file:
        logger.warning(f"文件不存在或无权访问: file_id={file_id}, user_id={user_id}")
        raise HTTPException(status_code=404, detail="文件不存在或无权访问")
    
    if not file.is_deleted:
        logger.warning(f"文件不在回收站中: file_id={file_id}")
        raise HTTPException(status_code=400, detail="文件不在回收站中")
    
    # 恢复文件
    file.is_deleted = False
    db.commit()
    
    logger.debug(f"恢复文件: {file_id}")
    return {"message": "文件恢复成功"}

def normalize_file_type(file):
    """正确化文件类型
    
    基于文件扩展名修复可能错误的文件类型
    
    Args:
        file: 文件对象
        
    Returns:
        str: 正确的文件类型
    """
    if not file.extension:
        return file.file_type
        
    # 去掉扩展名前面的点号
    ext = file.extension.lower().lstrip('.')
    
    # 图片扩展名
    if ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tiff']:
        return 'photo'
    
    # 视频扩展名
    if ext in ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm']:
        return 'video'
    
    # 音频扩展名
    if ext in ['mp3', 'wav', 'ogg', 'm4a', 'flac', 'aac']:
        return 'audio'
    
    # 文档扩展名
    if ext in ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'rtf']:
        return 'document'
    
    # 保持原有类型
    return file.file_type

def get_file_preview_url(db: Session, file_id: int, user_id: int) -> Dict[str, Any]:
    """获取文件预览URL
    
    Args:
        db: 数据库会话
        file_id: 文件ID
        user_id: 用户ID（用于权限验证）
    
    Returns:
        Dict[str, Any]: 包含预览URL和文件类型的字典
    """
    file = get_file_by_id(db, file_id, user_id)
    if not file:
        logger.warning(f"文件不存在或无权访问: file_id={file_id}, user_id={user_id}")
        raise HTTPException(status_code=404, detail="文件不存在或无权访问")
    
    if file.is_deleted:
        logger.warning(f"文件已被删除: file_id={file_id}")
        raise HTTPException(status_code=400, detail="文件已被删除")
    
    try:
        # 确保文件类型正确
        correct_file_type = normalize_file_type(file)
        
        # 获取不带点号的扩展名
        ext = file.extension.lower().lstrip('.') if file.extension else ''
        
        # 根据文件类型设置正确的 response_content_type
        if correct_file_type == 'photo':
            # 对于图片，根据扩展名设置具体的 MIME 类型
            mime_map = {
                'jpg': 'image/jpeg',
                'jpeg': 'image/jpeg',
                'png': 'image/png',
                'gif': 'image/gif',
                'bmp': 'image/bmp',
                'webp': 'image/webp',
                'svg': 'image/svg+xml',
                'tiff': 'image/tiff'
            }
            response_content_type = mime_map.get(ext, 'image/jpeg')
            
        elif correct_file_type == 'video':
            # 对于视频，根据扩展名设置具体的 MIME 类型
            mime_map = {
                'mp4': 'video/mp4',
                'avi': 'video/x-msvideo',
                'mov': 'video/quicktime',
                'wmv': 'video/x-ms-wmv',
                'flv': 'video/x-flv',
                'mkv': 'video/x-matroska',
                'webm': 'video/webm'
            }
            response_content_type = mime_map.get(ext, 'video/mp4')
            
        elif correct_file_type == 'audio':
            # 对于音频，根据扩展名设置具体的 MIME 类型
            mime_map = {
                'mp3': 'audio/mpeg',
                'wav': 'audio/wav',
                'ogg': 'audio/ogg',
                'm4a': 'audio/mp4',
                'flac': 'audio/flac',
                'aac': 'audio/aac'
            }
            response_content_type = mime_map.get(ext, 'audio/mpeg')
            
        elif correct_file_type == 'document':
            # 对于文档，根据扩展名设置具体的 MIME 类型
            mime_map = {
                'pdf': 'application/pdf',
                'doc': 'application/msword',
                'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                'xls': 'application/vnd.ms-excel',
                'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                'ppt': 'application/vnd.ms-powerpoint',
                'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
                'txt': 'text/plain',
                'csv': 'text/csv',
                'rtf': 'application/rtf'
            }
            response_content_type = mime_map.get(ext, 'application/octet-stream')
        else:
            # 其他类型，使用通用的二进制流类型
            response_content_type = 'application/octet-stream'
        
        # 记录 MIME 类型信息
        logger.debug(f"文件类型: {correct_file_type}, 扩展名: {ext}, MIME类型: {response_content_type}")
        
        # 生成预览URL，使用正确的 content_type
        # 添加安全检查，如果settings对象没有preview_expiry_seconds属性，则使用默认值3600秒
        preview_expiry = getattr(settings, "preview_expiry_seconds", 3600)
        logger.debug(f"使用预览链接过期时间: {preview_expiry}秒")
        
        preview_url = minio_client.get_presigned_url(
            object_path=file.file_path,
            expires=preview_expiry,  # 使用获取的过期时间，支持默认值
            response_content_type=response_content_type
        )
        
        # 增加预览支持相关信息
        preview_info = {
            "url": preview_url,
            "file_type": correct_file_type,  # 使用正确的文件类型
            "mime_type": response_content_type,  # 使用设置的 response_content_type
            "file_name": file.file_name,
            "file_size": file.file_size,
            "extension": file.extension,
            "direct_preview": correct_file_type in ["photo", "video"]
        }
            
        return preview_info
    except Exception as e:
        logger.error(f"生成预览URL失败: {str(e)}")
        raise HTTPException(status_code=500, detail="生成预览URL失败")

def get_file_download_url(db: Session, file_id: int, user_id: int) -> Dict[str, Any]:
    """获取文件下载URL
    
    Args:
        db: 数据库会话
        file_id: 文件ID
        user_id: 用户ID（用于权限验证）
    
    Returns:
        Dict[str, Any]: 包含下载URL和文件信息的字典
    """
    file = get_file_by_id(db, file_id, user_id)
    if not file:
        logger.warning(f"文件不存在或无权访问: file_id={file_id}, user_id={user_id}")
        raise HTTPException(status_code=404, detail="文件不存在或无权访问")
    
    if file.is_deleted:
        logger.warning(f"文件已被删除: file_id={file_id}")
        raise HTTPException(status_code=400, detail="文件已被删除")
    
    try:
        # 生成下载URL，从配置中读取过期时间，如果不存在则使用默认值
        download_expiry = getattr(settings, "download_expiry_seconds", 3600)
        logger.debug(f"使用下载链接过期时间: {download_expiry}秒")
        
        download_url = minio_client.get_presigned_url(
            object_path=file.file_path,
            expires=download_expiry,
            response_content_type=file.mime_type
        )
        
        return {
            "url": download_url,
            "file_name": file.file_name,
            "file_size": file.file_size,
            "mime_type": file.mime_type
        }
    except Exception as e:
        logger.error(f"生成下载URL失败: {str(e)}")
        raise HTTPException(status_code=500, detail="生成下载URL失败")

def batch_restore_files(db: Session, file_ids: List[int], user_id: int) -> Dict[str, Any]:
    """批量恢复文件
    
    Args:
        db: 数据库会话
        file_ids: 要恢复的文件ID列表
        user_id: 用户ID
        
    Returns:
        Dict[str, Any]: 包含成功和失败信息的字典
    """
    success_count = 0
    failed_count = 0
    failed_files = []
    
    for file_id in file_ids:
        try:
            # 查询文件是否存在
            file = db.query(File).filter(
                File.id == file_id,
                File.user_id == user_id,
                File.is_deleted == True
            ).first()
            
            if not file:
                failed_files.append({
                    "file_id": file_id,
                    "reason": "文件不存在或不在回收站中"
                })
                failed_count += 1
                continue
            
            # 恢复文件
            file.is_deleted = False
            db.commit()
            success_count += 1
            
        except Exception as e:
            logger.error(f"恢复文件失败: file_id={file_id}, error={str(e)}")
            failed_files.append({
                "file_id": file_id,
                "reason": f"恢复失败: {str(e)}"
            })
            failed_count += 1
            db.rollback()
    
    return {
        "success_count": success_count,
        "failed_count": failed_count,
        "failed_files": failed_files
    } 